package com.horse.cloud.framework.mybatis.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.horse.cloud.framework.mybatis.config.DatasourceExpansionConfigSupper;
import com.horse.cloud.framework.mybatis.exception.MybatisException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.AbstractDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @editor: intelliJ IDEA 16.1.3
 * @author: bing.Pan
 * @e-Mail: 15923508369@163.com
 * @gmDate: 2018/3/15/03/2018 14:20:下午2:20
 * @siDesc: 自动路由多数据源 Multifunction
 */

@Configuration
@Primary
public class MultiRouteDataSource extends AbstractDataSource implements ApplicationContextAware,InitializingBean,EnvironmentAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(MultiRouteDataSource.class);

    private Environment environment;

    private ApplicationContext context;
    private Map<Object, DataSource> targetDataSources = new HashMap<>();
    private DataSource defaultDataSource;


    /**
     * 重写此方法将多个数据源注册到容器中
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, DataSourceInfo> dataSourceInfoMap = new HashMap<>();
        //初始化主数据源
        initMasterDataSource(environment ,dataSourceInfoMap);
        //初始化slave数据源
        initSlaveDataSources(environment,dataSourceInfoMap);

        registerDataSources(dataSourceInfoMap);
    }



    private void initMasterDataSource(Environment environment, Map<String, DataSourceInfo> map) throws MybatisException {

        Binder binder = Binder.get(environment);

        List<String> nameList = binder.bind("horse.master-data-source.name", Bindable.listOf(String.class)).get();
        List<String> typeList = binder.bind("horse.master-data-source.type", Bindable.listOf(String.class)).get();
        List<String> connUrlList = binder.bind("horse.master-data-source.conn-url", Bindable.listOf(String.class)).get();
        List<String> userNameList = binder.bind("horse.master-data-source.user-name", Bindable.listOf(String.class)).get();
        List<String> passwordList = binder.bind("horse.master-data-source.password", Bindable.listOf(String.class)).get();
        List<String> driveClassNameList = binder.bind("horse.master-data-source.drive-class-name", Bindable.listOf(String.class)).get();

        if(nameList.isEmpty()){
            LOGGER.error("【framework-mybatis】未配置主数据源");
            throw new MybatisException("未配置主数据源");
        }
        int nameSize = nameList.size();
        int typeSize = typeList.size();
        int urlSize = connUrlList.size();
        int userNameSize = userNameList.size();
        int passwordSize = passwordList.size();
        int driveClassNameSize = driveClassNameList.size();

        if(nameSize != typeSize?true:typeSize != urlSize?true:urlSize != userNameSize?true:userNameSize != passwordSize?true:passwordSize != driveClassNameSize?true:false){
            LOGGER.error("【framework-mybatis】 主数据源信息配置不全");
            throw new MybatisException("主数据源信息配置不全");
        }

        for (int x = 0; x < nameSize; x ++){
            DataSourceInfo dataSourceInfo = new DataSourceInfo();
            dataSourceInfo.setMaster(true);
            dataSourceInfo.setDriveClassName(driveClassNameList.get(x));
            dataSourceInfo.setConnUrl(connUrlList.get(x));
            dataSourceInfo.setUserName(userNameList.get(x));
            dataSourceInfo.setPassword(passwordList.get(x));
            map.put(nameList.get(x) ,dataSourceInfo);
        }
    }







    private void initSlaveDataSources(Environment environment, Map<String, DataSourceInfo> map) throws MybatisException {

        Binder binder = Binder.get(environment);

        List<String> nameList = binder.bind("horse.multiple-data-source.name", Bindable.listOf(String.class)).get();
        List<String> typeList = binder.bind("horse.multiple-data-source.type", Bindable.listOf(String.class)).get();
        List<String> connUrlList = binder.bind("horse.multiple-data-source.conn-url", Bindable.listOf(String.class)).get();
        List<String> userNameList = binder.bind("horse.multiple-data-source.user-name", Bindable.listOf(String.class)).get();
        List<String> passwordList = binder.bind("horse.multiple-data-source.password", Bindable.listOf(String.class)).get();
        List<String> driveClassNameList = binder.bind("horse.multiple-data-source.drive-class-name", Bindable.listOf(String.class)).get();


        if(nameList.isEmpty()){
            LOGGER.info("【framework-mybatis】未配置slave数据源");
            return ;
        }
        int nameSize = nameList.size();
        int typeSize = typeList.size();
        int urlSize = connUrlList.size();
        int userNameSize = userNameList.size();
        int passwordSize = passwordList.size();
        int driveClassNameSize = driveClassNameList.size();

        if(nameSize != typeSize?true:typeSize != urlSize?true:urlSize != userNameSize?true:userNameSize != passwordSize?true:passwordSize != driveClassNameSize?true:false){
            LOGGER.error("【framework-mybatis】slave数据源配置信息不全");
            throw new MybatisException("从数据源信息配置不全");
        }


        for (int x = 0; x < nameSize; x ++){
            DataSourceInfo dataSourceInfo = new DataSourceInfo();
            dataSourceInfo.setMaster(false);
            dataSourceInfo.setDriveClassName(driveClassNameList.get(x));
            dataSourceInfo.setConnUrl(connUrlList.get(x));
            dataSourceInfo.setUserName(userNameList.get(x));
            dataSourceInfo.setPassword(passwordList.get(x));
            map.put(nameList.get(x) ,dataSourceInfo);
        }
    }



    private void registerDataSources(Map<String, DataSourceInfo> dataSourceInfoMap) throws MybatisException {

        if(dataSourceInfoMap.isEmpty()){ throw new MybatisException("未找到数据库配置信息"); }
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) this.context.getAutowireCapableBeanFactory();

        //连接池的补充设置
        Binder binder = Binder.get(environment);
        DatasourceExpansionConfigSupper datasourceExpansionConfigSupper = binder.bind("horse.expansion-datasource", DatasourceExpansionConfigSupper.class).get();

        dataSourceInfoMap.forEach((key,val) -> {
            DataSourceInfo dataSourceInfo = dataSourceInfoMap.get(key);
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);
            beanDefinitionBuilder.addPropertyValue("driverClassName",dataSourceInfo.getDriveClassName());
            beanDefinitionBuilder.addPropertyValue("url",dataSourceInfo.getConnUrl());
            beanDefinitionBuilder.addPropertyValue("username",dataSourceInfo.getUserName());
            beanDefinitionBuilder.addPropertyValue("password",dataSourceInfo.getPassword());

            beanDefinitionBuilder.addPropertyValue("initialSize", datasourceExpansionConfigSupper.getInitialSize());
            beanDefinitionBuilder.addPropertyValue("maxActive", datasourceExpansionConfigSupper.getMaxActive());
            beanDefinitionBuilder.addPropertyValue("minIdle", datasourceExpansionConfigSupper.getMinIdle());
            beanDefinitionBuilder.addPropertyValue("maxWait", datasourceExpansionConfigSupper.getMaxWait());
            beanDefinitionBuilder.addPropertyValue("defaultAutoCommit", datasourceExpansionConfigSupper.getDefaultAutoCommit());
            beanDefinitionBuilder.addPropertyValue("removeAbandoned", datasourceExpansionConfigSupper.getRemoveAbandoned());
            beanDefinitionBuilder.addPropertyValue("removeAbandonedTimeout",  datasourceExpansionConfigSupper.getRemoveAbandonedTimeout());
            beanDefinitionBuilder.addPropertyValue("numTestsPerEvictionRun", datasourceExpansionConfigSupper.getNumTestsPerEvictionRun());
            beanDefinitionBuilder.addPropertyValue("connectionProperties", datasourceExpansionConfigSupper.getConnectionProperties());
            beanDefinitionBuilder.addPropertyValue("validationQuery", datasourceExpansionConfigSupper.getValidationQuery());
            beanDefinitionBuilder.addPropertyValue("testWhileIdle", datasourceExpansionConfigSupper.getTestWhileIdle());
            beanDefinitionBuilder.addPropertyValue("minEvictableIdleTimeMillis", datasourceExpansionConfigSupper.getMinEvictableIdleTimeMillis());
            beanDefinitionBuilder.addPropertyValue("timeBetweenEvictionRunsMillis", datasourceExpansionConfigSupper.getTimeBetweenEvictionRunsMillis());
            beanDefinitionBuilder.addPropertyValue("testOnBorrow", datasourceExpansionConfigSupper.getTestOnBorrow());
            beanDefinitionBuilder.addPropertyValue("testOnReturn", datasourceExpansionConfigSupper.getTestOnReturn());


            beanFactory.registerBeanDefinition(key,beanDefinitionBuilder.getRawBeanDefinition());

            DruidDataSource druidDataSource = (DruidDataSource)this.context.getBean(key);
            targetDataSources.put(key,druidDataSource);

            if(dataSourceInfo.getMaster()){ defaultDataSource = druidDataSource; }


            DataSourceContextHolder.get().registerDataSourceKey(key,dataSourceInfo.getMaster());


        });

    }



    /**
     * 获取执行数据库连接信息
     * @return   DataSource
     */
    private DataSource determineTargetDataSource(){
        Object lookupKey = DataSourceContextHolder.get().getDataSourceKey();
        if(lookupKey == null){
            return defaultDataSource;
        }
        DataSource dataSource = targetDataSources.get(lookupKey);
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }


    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }






    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;

    }
}
